#pragma once
#include <iomanip>
#include <iostream>
#include <string>
#include <fstream>
using namespace std;

template<class T> struct Element
{
		T element_info;
		Element <T> *next;
		Element()
		{
			element_info = NULL;
			next = nullptr;
		}
		Element(T einfo)
		{
			this->element_info = einfo;
			this->next = nullptr;
		}
		~Element(){}
};

template<class T> class List
{
		class Iterator
		{
		public:
				typedef Iterator self_type;
				typedef Element<T> value_type;
				typedef Element<T>& reference;
				typedef Element<T>* pointer;
				typedef std::forward_iterator_tag iterator_category;
				typedef int difference_type;

				Iterator(pointer ptr) : ptr_(ptr) { } //Constructor
				self_type operator++() { self_type i = *this; ptr_=ptr_->next; return i; }
				self_type operator++(int junk) { ptr_=ptr_->next; return *this; }
				reference operator*() { return *ptr_; }
				pointer operator->() { return ptr_; }
				bool operator==(const self_type& rhs) { return ptr_ == rhs.ptr_; }
				bool operator!=(const self_type& rhs) { return ptr_ != rhs.ptr_; }

		private:
				pointer ptr_;
		};
		
	public:
		Element<T> *head;
		Element<T> *tail;

		friend istream& operator >>(istream &in, T &a)
		{
			in >> a >> skipws;
			return in;
		}

		friend ostream& operator <<(ostream &out, List<T> &list)
		{
			Element<T> *e = list.head;
			while (e != nullptr)
			{
				out << e->element_info << " ";
				e = e->next;
			}
			return out;
		}
			
		Iterator begin()
		{
			return Iterator(head);
		}

		Iterator end()
		{
			return Iterator(nullptr);
		}

		List()
		{
			head = tail = nullptr;
		}
		~List()
		{
			Element <T> *e = head;
			Element <T> *d;
			while (e != nullptr)
			{
				d = e;
				e = e -> next;
				delete d;
			}
		}

		List (List &l)
		{
			Element<T> *n = l.head;
			int k = 0;
			while(n != nullptr)
			{
				if(k == 0)
					this->addAtTail(n->element_info);
				this->addAtHead(n->element_info);
				n = n->next;
				k = 1;
			}
		}

		List & operator =(List &l)
		{
			Element<T> *n = l.head;
			while(n != nullptr)
			{
				this->addAtHead(n->element_info);
				n = n->next;
			}
			return *this;
		}
				
		void addAtHead(T &element_info)
		{
			if ( head == nullptr )
			{
				head = tail = new Element<T>(element_info);
			}
			else
			{
				Element<T> *e = new Element<T>(element_info);
				e -> next = head;
				head = e;
			}
		}

		void  addAtTail(T &element_info)
		{
			if(tail == nullptr)
				head = tail = new Element<T>(element_info);
			else
			{
				Element<T> *e = new Element<T>(element_info);
				tail = e;
				head = nullptr;
			}
		}
	
};



